/*
 * Copyright (c) 2012, 2014, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of The Linux Foundation nor
 *     the names of its contributors may be used to endorse or promote
 *     products derived from this software without specific prior written
 *     permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdint.h>
#include <debug.h>
#include <reg.h>
#include <err.h>
#include <limits.h>
#include <bits.h>
#include <clock.h>
#include <string.h>

static struct clk_list msm_clk_list;

int clk_set_parent(struct clk *clk, struct clk *parent)
{
	if (!clk->ops->set_parent)
		return 0;

	return clk->ops->set_parent(clk, parent);
}

struct clk *clk_get_parent(struct clk *clk)
{
	if (!clk->ops->get_parent)
		return NULL;

	return clk->ops->get_parent(clk);
}

int clk_reset(struct clk *clk, enum clk_reset_action action)
{
	if (!clk)
		return 0;

	if (!clk->ops->reset)
		return 0;

	return clk->ops->reset(clk, action);
}

/*
 * Standard clock functions defined in include/clk.h
 */
int clk_enable(struct clk *clk)
{
	int ret = 0;
	struct clk *parent;

	if (!clk)
		return 0;

	if (clk->count == 0) {
		parent = clk_get_parent(clk);
		ret = clk_enable(parent);
		if (ret)
			goto out;

		if (clk->ops->enable)
			ret = clk->ops->enable(clk);
		if (ret) {
			clk_disable(parent);
			goto out;
		}
	}
	clk->count++;
out:
	return ret;
}

void clk_disable(struct clk *clk)
{
	struct clk *parent;

	if (!clk)
		return;

	if (clk->count == 0)
		goto out;
	if (clk->count == 1) {
		if (clk->ops->disable)
			clk->ops->disable(clk);
		parent = clk_get_parent(clk);
		clk_disable(parent);
	}
	clk->count--;
out:
	return;
}

unsigned long clk_get_rate(struct clk *clk)
{
	if (!clk->ops->get_rate)
		return 0;

	return clk->ops->get_rate(clk);
}

int clk_set_rate(struct clk *clk, unsigned long rate)
{
	if (!clk->ops->set_rate)
		return ERR_NOT_VALID;

	return clk->ops->set_rate(clk, rate);
}

void clk_init(struct clk_lookup *clist, unsigned num)
{
	if(clist && num)
	{
		msm_clk_list.clist = (struct clk_lookup *)clist;
		msm_clk_list.num = num;
	}
}

struct clk *clk_get (const char * cid)
{
	unsigned i;
	struct clk_lookup *cl= msm_clk_list.clist;
	unsigned num = msm_clk_list.num;

	if(!cl || !num)
	{
		dprintf (CRITICAL, "Alert!! clock list not defined!\n");
		return NULL;
	}
	for(i=0; i < num; i++, cl++)
	{
		if(!strcmp(cl->con_id, cid))
		{
			return cl->clk;
		}
	}

	dprintf(CRITICAL, "Alert!! Requested clock \"%s\" is not supported!", cid);
	return NULL;
}

int clk_get_set_enable(char *id, unsigned long rate, bool enable)
{
	int ret = NO_ERROR;
	struct clk *cp;

	/* Get clk */
	cp = clk_get(id);
	if(!cp)
	{
		dprintf(CRITICAL, "Can't find clock with id: %s\n", id);
		ret = ERR_NOT_VALID;
		goto get_set_enable_error;
	}

	/* Set rate */
	if(rate)
	{
		ret = clk_set_rate(cp, rate);
		if(ret)
		{
			dprintf(CRITICAL, "Clock set rate failed.\n");
			goto get_set_enable_error;
		}
	}

	/* Enable clock */
	if(enable)
	{
		ret = clk_enable(cp);
		if(ret)
		{
			dprintf(CRITICAL, "Clock enable failed.\n");
		}
	}

get_set_enable_error:
	return ret;
}

#ifdef DEBUG_CLOCK
struct clk_list *clk_get_list()
{
	return &msm_clk_list;
}
#endif
